#include "pch.hpp"
#include "Parser.hpp"

#include "CssSymbols.hpp"
#include "CssParser.hpp"
#include "Text.hpp"

using namespace Engine;
using namespace Engine::Ui;




Parser::Parser(BasicStyleDefinition* styleDefinition)
	: m_styleDefinition(styleDefinition)
{}
///////////////////////////////////////////////////////////////////////////////////////////////////




bool Parser::parse(QString code)
{
	m_errors.clear();

	CssDocument doc;
	CssParser p;
	p.registerAttribute(Attributes::BackgroundColor, &Parser::color);
	p.registerAttribute(Attributes::ForegroundColor, &Parser::color);
	p.registerAttribute(Attributes::FontColor,       &Parser::color);

	p.registerAttribute(Attributes::BackgroundGlyph,            boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphTopLeft,     boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphTop,         boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphTopRight,    boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphRight,       boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphBottomRight, boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphBottom,      boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphBottomLeft,  boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::BackgroundGlyphLeft,        boost::bind(&Parser::glyph, this, _1));

	p.registerAttribute(Attributes::ForegroundGlyph,            boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphTopLeft,     boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphTop,         boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphTopRight,    boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphRight,       boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphBottomRight, boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphBottom,      boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphBottomLeft,  boost::bind(&Parser::glyph, this, _1));
	p.registerAttribute(Attributes::ForegroundGlyphLeft,        boost::bind(&Parser::glyph, this, _1));

	p.registerAttribute(Attributes::Font,        &Parser::string);
	p.registerAttribute(Attributes::FontSize,    &Parser::size);
	p.registerAttribute(Attributes::FontOutline, &Parser::size);

	p.registerAttribute(Attributes::MinWidth,  &Parser::size);
	p.registerAttribute(Attributes::MaxWidth,  &Parser::size);
	p.registerAttribute(Attributes::MinHeight, &Parser::size);
	p.registerAttribute(Attributes::MaxHeight, &Parser::size);

	p.registerAttribute(Attributes::Resizing, &Parser::resizing);

	bool b = p.parse(code, doc, m_errors);
	if(!b)
		return false;

	foreach(const CssClass::ptr& c, doc.classes())
	{
		WidgetStyle::ptr style(createStyle(*c));

		// There is no verification if a font exists until rendering
		// We must do it here or an invalid style might be used for rendering
		if(c->type() == Widgets::Text)
		{
			try
			{
				FontDesc desc = Text::fontDesc(style, WidgetState::Normal);
				m_styleDefinition->font(desc);
			}
			catch(font_missing_exception& e)
			{
				m_errors.push_back(Error(e.what()));
			}
			catch(attribute_missing_exception& e)
			{
				m_errors.push_back(Error(e.what()));
			}
		}

		m_styleDefinition->add(createIdentifier(*c),style);
	}

	return m_errors.size() == 0;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

const Parser::Errors& Parser::errors() const
{
	return m_errors;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boost::any Parser::string(const std::string& str)
{
	QString data(str.c_str());

	if(data.startsWith('\"'))
		data = data.right(data.size() - 1);
	if(data.endsWith('\"'))
		data = data.left(data.size() - 1);

	return data;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boost::any Parser::glyph(const std::string& name)
{
	QString _name = name.c_str();
	_name.replace('\"', "");
	return m_styleDefinition->glyph(_name);
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boost::any Parser::color(const std::string& _data)
{
	// Remove all but one whitespace and all whitespace at the start and end
	QString data = _data.c_str();
	data = data.simplified();

	// It could start with #, followed by a hexdezimal code
	if(data.startsWith('#'))
	{
		data = data.right(data.size() - 1);
		BOOSTEXT_THROW(not_implemented_exception());
	}
	// It could be a comma or space separated list of decimal numbers
	else if(data.count(',') == 3 || data.count(' ') == 3)
	{
		QStringList numbers;

		if(data.count(',') == 3)
			numbers = data.split(',');
		else
			numbers = data.split(' ');

		bool error = false;
		uint32 r = numbers[0].toUInt(&error);
		if(!error)
			BOOSTEXT_THROW(exception(format("Expected number in the range of [0, 255]: %1%") % numbers[0]));

		uint32 g = numbers[1].toUInt(&error);
		if(!error)
			BOOSTEXT_THROW(exception(format("Expected number in the range of [0, 255]: %1%") % numbers[1]));

		uint32 b = numbers[2].toUInt(&error);
		if(!error)
			BOOSTEXT_THROW(exception(format("Expected number in the range of [0, 255]: %1%") % numbers[2]));

		uint32 a = numbers[3].toUInt(&error);
		if(!error)
			BOOSTEXT_THROW(exception(format("Expected number in the range of [0, 255]: %1%") % numbers[3]));

		return math::color(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
	}
	else
	{
		if(data == "white")               return math::color::white();
		else if(data == "black")          return math::color::black();
		else if(data == "red")            return math::color::red();
		else if(data == "green")          return math::color::green();
		else if(data == "blue")           return math::color::blue();
		else if(data == "cyan")           return math::color::cyan();
		else if(data == "purple")         return math::color::purple();
		else if(data == "yellow")         return math::color::yellow();
		else if(data == "grey")           return math::color::grey();
		else if(data == "transparent" ||
				data == "none")           return math::color(0, 0, 0, 0);
		else BOOSTEXT_THROW(exception(format("'%1%' is not a color") % _data));
	}
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boost::any Parser::size(const std::string& _data)
{
	QString data = _data.c_str();
	if(!data.endsWith("px"))
		BOOSTEXT_THROW(exception(format("Expected size: %1%") % _data));

	data = data.left(data.size() - 2);
	bool b = false;
	std::size_t size = data.toUInt(&b);
	if(!b)
		BOOSTEXT_THROW(exception(format("Expected number: %1%") % _data));

	return size;
}
///////////////////////////////////////////////////////////////////////////////////////////////////

boost::any Parser::resizing(const std::string& data)
{
	if(data == "normal")      return  Resizing::Standard;
	else if(data == "greedy") return  Resizing::Greedy;
	else if(data == "minimum") return Resizing::Minimum;
	else BOOSTEXT_THROW(exception(format("Expected resizing type: %1%") % data));
}
///////////////////////////////////////////////////////////////////////////////////////////////////

WidgetStyle::ptr Parser::createStyle(CssClass& c)
{
	auto parent = c.defaultClass().lock();
	WidgetStyle::ptr parentStyle;
	if(parent)
	{
		WidgetStyleIdentifier id = createIdentifier(*parent);
		parentStyle = m_styleDefinition->widgetStyle(id);
	}

	return boost::make_shared<WidgetStyle>(parentStyle, c.attributes());
}
///////////////////////////////////////////////////////////////////////////////////////////////////

WidgetStyleIdentifier Parser::createIdentifier(const CssClass& c)
{
	WidgetStyleIdentifier styleId;
	styleId.reserve(c.parents().size() + 1);
	styleId.push_back( StyleIdentifier(c.name(), c.type()) );
	foreach(const CssClass::Parent& parent, c.parents())
	{
		styleId.push_back(StyleIdentifier(parent.second, parent.first));
	}

	return styleId;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
